import numpy as np
import pandas as pd

from layers import LinearLayer, SigmoidLayer
from losses import SquaredError

from sklearn import model_selection
import pandas as pd
import matplotlib.pyplot as plt

import sys
sys.path.append("..")
'''
多层感知机和bp算法
'''
# =============== step.0 加载数据 ===============
df = pd.read_csv('./data/watermelon-3.0-num.csv')
features = df[['色泽','根蒂','敲声','纹理','脐部','触感','密度','含糖率']].to_numpy()
labels = df[['好瓜']].to_numpy()

num_features = features.shape[1]  # 特征个数
num_hidden_neurals = 8  # 隐藏层神经元
num_outputs = 1  # 输出

# =============== step.1 确定网络 ===============
net = [
    LinearLayer(num_features, num_hidden_neurals),  # 输入层z = wx + b
    SigmoidLayer(),  # 激活函数
    LinearLayer(num_hidden_neurals, num_outputs),   # 隐藏层
    SigmoidLayer(),  # 输出层
]
loss = SquaredError  # 误差函数


def forward(x):
    """正向传播"""
    out = x
    for layer in net:
        out = layer._forward(out)

    return out


def backward(grad, lr):
    """反向传播"""
    grad_prev = grad
    for layer in reversed(net):
        grad_prev = layer._backward(grad_prev, lr=lr)

def accuracy(x, y):
    out = forward(x)

    right_count = 0.
    for i in range(len(out)):
        if out[i] >= 0.5 and y[i] == 1:
            right_count += 1
        elif out[i] < 0.5 and y[i] == 0:
            right_count += 1

    return right_count/ len(out)


# =============== step.2 训练 ===============
# lr = 0.005  # 学习率
lr = 0.8  # 学习率
losses = []
accs = []
totoal_accs = []

epoch = 100000

losses_epoch = []
for ep in range(epoch):
    X_train, X_test, Y_train, Y_test = model_selection.train_test_split(features, labels, test_size=0.25)

    y_hat = forward(X_train)

    e = loss.fn(y_hat, Y_train)
    losses_epoch.append(e)
    
    grad_e_y = loss.grad(y_hat, Y_train)
    backward(grad_e_y, lr = lr)

    acc = accuracy(X_test, Y_test)
    loss_mean = np.mean(e)
    total_acc = accuracy(features, labels)

    accs.append(acc)
    losses.append(loss_mean)
    totoal_accs.append(total_acc)

    print("epoch:{} loss:{}, acc:{}".format(ep, loss_mean, acc))

    #退出条件
    if(acc > 0.9 and loss_mean < 0.1): break


print('totoal accuarcy:', totoal_accs[-1])

plt.title("mean loss and accuracy per hundred epoch")
x = np.arange(len(losses[::100]))
plt.plot(x, losses[::100], label="loss")
plt.plot(x, accs[::100], label="test accuracy", linestyle='-.')
plt.plot(x, totoal_accs[::100], label="total accuracy", linestyle='-')
plt.xlabel("epoch/ hundred")

plt.text(0.1, 0.03, 'loss:{:.3f}, totoal accuracy:{:.3f}'.format(losses[-1], totoal_accs[-1]),fontsize='large')
plt.legend()
plt.show()
